﻿using System;
using System.IO;
using System.Runtime.InteropServices;
using DirectShowLib;
using DirectShowLib.Utils;
using System.Collections.Generic;

namespace MediaPlayer
{
    public class Graph : IDisposable
    {
        private IntPtr handle;
        private DsROTEntry rot;

        public IGraphBuilder Builder { get; private set; }
        public IMediaControl Control { get; private set; }
        public IMediaEventEx Events { get; private set; }
        public IVideoWindow Window { get; private set; }
        public IBasicAudio Audio { get; private set; }
        public IMediaSeeking Seeking { get; private set; }
        public IMediaPosition Position { get; private set; }
        public Renderers.Basic Renderer { get; set; }

        public Graph(IntPtr handle)
        {
            this.handle = handle;

            this.Builder = (IGraphBuilder)new FilterGraph();

            this.Control = (IMediaControl)this.Builder;
            this.Events = (IMediaEventEx)this.Builder;
            this.Seeking = (IMediaSeeking)this.Builder;
            this.Position = (IMediaPosition)this.Builder;
            this.Audio = this.Builder as IBasicAudio;

            this.rot = new DsROTEntry(this.Builder);

            // Have the graph signal event via window callbacks for performance
            int hr = this.Events.SetNotifyWindow(this.handle, 0x0400 + 13, IntPtr.Zero); // WM_GRAPHNOTIFY
            DsError.ThrowExceptionForHR(hr);
        }

        public List<string> GetFilters()
        {
            IEnumFilters enumFilters;
            this.Builder.EnumFilters(out enumFilters);

            IBaseFilter[] filters = new IBaseFilter[1];
            IntPtr fetched = IntPtr.Zero;

            List<string> filterNames = new List<string>();

            while (enumFilters.Next(1, filters, fetched) == 0)
            {
                FilterInfo info;
                filters[0].QueryFilterInfo(out info);

                // Debug
                System.Diagnostics.Debug.WriteLine("Filter: " + info.achName);

                filterNames.Add(info.achName);
            }

            return filterNames;
        }

        public void AddFilter(IBaseFilter filter, string name)
        {
            int hr = this.Builder.AddFilter(filter, name);
            DsError.ThrowExceptionForHR(hr);
        }

        public void RemoveFilter(IBaseFilter filter)
        {
            int hr = this.Builder.RemoveFilter(filter);
            DsError.ThrowExceptionForHR(hr);
        }

        public void RemoveAllFilters()
        {
            FilterGraphTools.RemoveAllFilters(this.Builder);
        }

        public void RenderFile(string file)
        {
            if (!File.Exists(file)) throw new FileNotFoundException(file);

            int hr = this.Builder.RenderFile(file, null);
            DsError.ThrowExceptionForHR(hr);
        }

        protected void Dispose(Boolean freeObjects)
        {
            try
            {
                if (!freeObjects) return;

                lock (this)
                {
                    this.rot.Dispose();
                    this.rot = null;

                    //int hr = this.Window.put_Visible(OABool.False);
                    //DsError.ThrowExceptionForHR(hr);
                    //hr = this.Window.put_Owner(IntPtr.Zero);
                    //DsError.ThrowExceptionForHR(hr);

                    if (this.Events != null)
                    {
                        int hr = this.Events.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero);
                        DsError.ThrowExceptionForHR(hr);
                    }

                    if (this.Renderer != null) this.Renderer.Dispose();
                    this.Renderer = null;

                    this.Builder.Abort();

                    RemoveAllFilters();
     
                    if (this.Builder != null) Marshal.ReleaseComObject(this.Builder);
                    this.Builder = null;
                    this.Events = null;
                    this.Seeking = null;
                    this.Position = null;
                    this.Control = null;
                    this.Audio = null;
                    this.Window = null;
                }
            }
            finally
            {
                GC.Collect();
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~Graph()
        {
            Dispose(false);
        }
    }
}
